using System;
using Microsoft.Xna.Framework;
using SharpWind.Resources;
using SharpWind.Graphics.Text;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using SharpWind.Network;
using Lidgren.Library.Network;

namespace SharpWind.Helpers
{
	public class PerfomanceMonitor
	{
		#region Fields

		// FPS variables
		private int totalFrames = 0;

		private double bestFps = double.MinValue;

		private double worstFps = double.MaxValue;

		private double averageFps = 0;

		private double currentFps = 0;

		// Frame time variables
		private double lastFrameTime = 0;

		private double averageFrameTime = 0;

		private double bestFrameTime = double.MaxValue;

		private double worstFrameTime = double.MinValue;

		// Network variables
		private double ping = 0;

		// Reset variables
		private double resetTimeMS = 0;

		private double resetTimeS = 0;

		private bool reset = false;

		// Drawing variables
		private TextBlock textBlock = null;

		private double updateTime = 0f;

		#endregion

		#region Constructors

		public PerfomanceMonitor()
		{
			Engine.Instance.Content.Fonts.Add( "Courier", new Font( "Courier.spritefont" ) );
			this.textBlock = new TextBlock( Engine.Instance.Content.Fonts["Courier"], string.Empty,
											new Vector2( 10, 10 ), Color.White );
			Engine.Instance.Texts.Add( this.textBlock );
		}

		#endregion

		#region Methods

		public void Reset()
		{
			this.reset = true;
		}

		private void Reset( GameTime gameTime )
		{
			this.totalFrames = 0;
			this.averageFps = 0;
			this.bestFps = double.MinValue;
			this.worstFps = double.MaxValue;
			this.averageFrameTime = 0;
			this.bestFrameTime = double.MaxValue;
			this.worstFrameTime = double.MinValue;
			this.resetTimeMS += ( gameTime.TotalGameTime.TotalMilliseconds - resetTimeMS );
			this.resetTimeS += ( gameTime.TotalGameTime.TotalSeconds - resetTimeS );
		}

		internal void Update( GameTime gameTime )
		{
			if( this.reset )
			{
				Reset( gameTime );
				this.reset = false;
			}

			#region Fps Calculations

			this.totalFrames++;

			this.currentFps = 1 / gameTime.ElapsedGameTime.TotalSeconds;

			this.averageFps = this.totalFrames / ( gameTime.TotalGameTime.TotalSeconds - resetTimeS );

			if( this.currentFps > this.bestFps && !double.IsInfinity( this.currentFps ) )
			{
				this.bestFps = this.currentFps;
			}

			if( this.currentFps < this.worstFps )
			{
				this.worstFps = this.currentFps;
			}

			#endregion

			#region Frame time Calculations

			this.lastFrameTime = 1000 / this.currentFps;

			this.averageFrameTime = ( gameTime.TotalGameTime.TotalMilliseconds - resetTimeMS ) / this.totalFrames;

			if( this.lastFrameTime < this.bestFrameTime && this.lastFrameTime != 0 )
			{
				this.bestFrameTime = this.lastFrameTime;
			}

			if( this.lastFrameTime > this.worstFrameTime )
			{
				this.worstFrameTime = this.lastFrameTime;
			}

			#endregion
		}

		internal void Draw( GameTime gameTime )
		{
			this.updateTime += gameTime.ElapsedGameTime.TotalMilliseconds;

			if( this.updateTime >= 100 )
			{
				this.updateTime = 0;

				StringBuilder sb = new StringBuilder();
				sb.AppendLine( "FPS Statistics (Higher is better)" );
				sb.AppendLine( string.Format( "  Current: {0}", this.currentFps = double.IsInfinity( this.currentFps ) ? 0 : this.currentFps ) );
				sb.AppendLine( string.Format( "  Average: {0}", this.averageFps = double.IsInfinity( this.averageFps ) ? 0 : this.averageFps ) );
				sb.AppendLine( string.Format( "  Best: {0}", this.bestFps ) );
				sb.AppendLine( string.Format( "  Worst: {0}", this.worstFps ) );
				sb.AppendLine( "Frame time Statistics (Lower is better)" );
				sb.AppendLine( string.Format( "  Last: {0}", this.lastFrameTime ) );
				sb.AppendLine( string.Format( "  Average: {0}", this.averageFrameTime ) );
				sb.AppendLine( string.Format( "  Best: {0}", this.bestFrameTime ) );
				sb.AppendLine( string.Format( "  Worst: {0}", this.worstFrameTime ) );
				sb.AppendLine( "Network Statistics" );
				sb.AppendLine( string.Format( "  Status: {0}", Engine.Instance.Network.Status.ToString() ) );
				if( !string.IsNullOrEmpty( Engine.Instance.Network.CurrentReason ) )
				{
					sb.AppendLine( string.Format( "  Reason: {0}", Engine.Instance.Network.CurrentReason ) );
				}
				if( Engine.Instance.Network.Status == NetConnectionStatus.Connected )
				{
					Connection serverConnection = Engine.Instance.Network.ServerConnection;

					sb.AppendLine( string.Format( "  Connected to: {0}", serverConnection.EndPoint.ToString() ) );
					sb.AppendLine( string.Format( "  Average Ping: {0}", serverConnection.AverageRoundtripTime ) );
					sb.AppendLine( "  Packets" );
					sb.AppendLine( string.Format( "    Sent: {0}", serverConnection.Statistics.PacketsSent ) ); 
					sb.AppendLine( string.Format( "    Received: {0}", serverConnection.Statistics.PacketsReceived ) );
					sb.AppendLine( "  Messages" );
					sb.AppendLine( string.Format( "    Sent: {0}", serverConnection.Statistics.MessagesSent ) );
					sb.AppendLine( string.Format( "    Resent: {0}", serverConnection.Statistics.MessagesResent ) );
					sb.AppendLine( string.Format( "    Received: {0}", serverConnection.Statistics.MessagesReceived ) );
					sb.AppendLine( "  User Messages" );
					sb.AppendLine( string.Format( "    Sent: {0}", serverConnection.Statistics.UserMessagesSent ) );
					sb.AppendLine( string.Format( "    Received: {0}", serverConnection.Statistics.UserMessagesReceived ) );
					sb.AppendLine( "  Ack Messages"  );
					sb.AppendLine( string.Format( "    Sent: {0}", serverConnection.Statistics.AckMessagesSent ) );
					sb.AppendLine( string.Format( "    Received: {0}", serverConnection.Statistics.AckMessagesReceived ) );
					sb.AppendLine( "  Bytes" );
					sb.AppendLine( string.Format( "    Sent: {0}", serverConnection.Statistics.BytesSent ) );
					sb.AppendLine( string.Format( "    Received: {0}", serverConnection.Statistics.BytesReceived ) );

				}
				sb.AppendLine();
				sb.AppendLine( string.Format( "Total frames: {0}", this.totalFrames ) );
				sb.AppendLine( string.Format( "Running time: {0}", gameTime.TotalGameTime.ToString() ) );

				this.textBlock.Text = sb.ToString();
			}
		}

		#endregion

		#region Properties

		public bool Visible
		{
			get
			{
				return this.textBlock.Visible;
			}
			set
			{
				this.textBlock.Visible = value;
			}
		}

		#endregion
	}
}
